package odfconverter;

import java.util.Stack;

public class OoxSpacesPostProcessor extends AbstractPostProcessor {
	//<a:t>
	//办公套件开放 
	//<pxs:s pxs:c="2" xmlns:pxs="urn:cleverage:xmlns:post-processings:extra-spaces" /> 
	//源码 
	//</a:t>
	//经过OoxSpacesPostProcessor后，变为
	//<a:t>办公套件开放  源码</a:t>
	
    private final static String W_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
    private final static String PXS_NAMESPACE = "urn:cleverage:xmlns:post-processings:extra-spaces";
   	private final static String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
   	private final static String DEFAULT_NAMESPACE = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
   	private final static String DRAWING_NAMESPACE = "http://schemas.openxmlformats.org/drawingml/2006/main";
	
    private Stack currentNode;
    private Stack context;
    private Stack store;
    
       
	protected OoxSpacesPostProcessor(XmlWriter nextWriter) throws Exception {
		super(nextWriter);
        this.currentNode = new Stack();
        this.context = new Stack();
        this.store = new Stack();
	}

	@Override
	public void WriteEndAttribute() {
    	if (InS())
    	{
    		EndSAttribute();
    	}
    	else if (InT())
    	{
    		EndTAttribute();
    	}
        else
        {
            this.nextWriter.WriteEndAttribute();
        }
        this.currentNode.pop();
	}

	@Override
	public void WriteEndElement() {
    	if (IsS())
    	{
    		EndS();
    	}
    	else if (IsT())
    	{
    		EndT();
    	}
    	else if (IsP())
    	{
    		EndP();
    	}
    	else if (IsSi())
    	{
    		EndSi();
    	}
        else
        {
        	this.nextWriter.WriteEndElement();
        }
        this.currentNode.pop();
	}

	@Override
	public void WriteStartAttribute(String prefix, String localName, String ns) {
        this.currentNode.push(new Attribute(prefix, localName, ns));
        if (InS())
        {
        	StartSAttribute();
        }
        else if (InT())
        {
        	StartTAttribute();
        }
        else
        {
            this.nextWriter.WriteStartAttribute(prefix, localName, ns);
        }
	}

	@Override
	public void WriteStartElement(String prefix, String localName, String ns) {
        this.currentNode.push(new Element(prefix, localName, ns));

        if (IsS())
        {
        	StartS();
        }
        else if (IsT())
        {
        	StartT();
        } 
        else if (IsP())
        {
        	StartP();
        }
        else if (IsSi())
        {
        	StartSi();
        }
        else
        {
            this.nextWriter.WriteStartElement(prefix, localName, ns);
        }
	}

	@Override
	public void WriteString(String text) {
    	if (InS())
    	{
    		WriteSString(text);
    	}
    	else if (InT())
    	{
            WriteTString(text.replace("\n","\n "));
      	}
        else
        {
            this.nextWriter.WriteString(text);
        }
	}
	
    /**
     * text elements
     */
     private boolean IsT()
     {
     	Element e = (Element) this.currentNode.peek();
     	return "t".equals(e.getName()) && (W_NAMESPACE.equals(e.getNs()) || DEFAULT_NAMESPACE.equals(e.getNs()) || DRAWING_NAMESPACE.equals(e.getNs()));
     }
    
     private boolean InT()
     {
     	return this.context.contains("t");
     }
     
     private void StartT()
     {
     	Element e = (Element) this.currentNode.peek();
     	this.store.push(e);
     	this.context.push("t");
     }
    
     private void EndT()
     {
     	Element t = (Element) this.store.pop();
     	if (this.preserveSpace && t.GetAttribute("space", XML_NAMESPACE) == null && !t.getNs().equals(DRAWING_NAMESPACE))
     	{
     		this.preserveSpace = false;
     		t.AddAttribute(new Attribute("xml", "space", "preserve", XML_NAMESPACE));
    	}
     	try {
			t.Write(nextWriter);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
     	
     	if (this.isFirstTextNode)
     	{
     		this.isFirstTextNode = false;
     	}
     	
     	this.context.pop();
     }
     
     private void StartTAttribute()
     {
     	Attribute a = (Attribute) this.currentNode.peek();
     	Element t = (Element) this.store.peek();
     	t.AddAttribute(a);
     	this.store.push(a);
     }
     
     private void EndTAttribute()
     {
     	this.store.pop();
     }
     
     private void WriteTString(String val)
     {
     	Node node = (Node) this.currentNode.peek();
     	
     	if (node instanceof Element)
     	{
     		Element t = (Element) node;
     		String trim = Clean(val);
     		
     		// Two more cases to handle :
     		// 1. there shouldn't be a white space as the first character
     		// of a paragraph or string item.
     		if (this.isFirstTextNode && trim.indexOf('\u0020') == 0)
     		{
     			trim = trim.substring(1);
     		}
     		// 2. remove any begining white space following a <text:s/>
     		else if (this.followS) 
     		{
     			this.followS = false;
     			
     			if (trim.indexOf('\u0020') == 0)
     			{
     				trim = trim.substring(1);
     			}
     		}
     		
     		t.AddChild(trim);
     	}
     	else 
     	{
     		Attribute a = (Attribute) node;
     		if (a.getValue() != null) {
     		    a.setValue(a.getValue()+val);
     		}else{
     			a.setValue(val);
     		}
     	}
     }
     
     // Removes n-1 consecutive white spaces
     private String Clean(String val)
     {
     	StringBuilder sb = new StringBuilder();
     	char [] c = val.toCharArray();
     	int i = 0;
     	
     	while (i < c.length)
     	{
     		if (c[i] != '\n')
     		{
     			if (i < c.length - 1)
     			{
     				if (!((c[i]=='\u0020' || c[i]=='\n') && 
     				      (c[i+1]=='\u0020' || c[i+1]=='\n')))
     				{
     					sb.append(c[i]);
     				}
     			}
     			else
     			{
     				sb.append(c[i]);
     			}
     		}
     		i++;
     	}
    	
     	return sb.toString();
     }
 
     /**
      * Convert extra spaces tag to a sequence of white space characters
      */
     private int cSpaces;
     private boolean followS = false;
     private boolean preserveSpace = false;
    
     private boolean IsS()
     {
     	Element e = (Element) this.currentNode.peek();
     	return "s".equals(e.getName()) && PXS_NAMESPACE.equals(e.getNs());
     }
     
     private boolean InS()
     {
     	return this.context.contains("s");
     }
     
     private void StartS()
     {
     	this.context.push("s");
     }

     private void EndS()
     {
     	this.context.pop();
     	String spaces = null;
     	String space = String.valueOf('\u0020');
     	
     	if (this.cSpaces > 0)
     	{
     		for(int i=0;i<this.cSpaces;i++)
     		{
     			if(i==0){
     				spaces = space;
     			}else{
     			    spaces = spaces + space;
     			}
     		}
     		//spaces = new String('\u0020', this.cSpaces);	
     	}
     	else 
     	{
     		spaces = String.valueOf('\u0020');
     	}
     	this.cSpaces = 0;
     	
     	if (InT())
     	{
     		Element t = (Element) this.store.peek();
     		t.AddChild(spaces);
     		this.preserveSpace = true;
     		this.followS = true;
     	}
     }
     
     private void StartSAttribute()
     {
     	this.context.push("c-attr");
     }
     
     private void EndSAttribute()
     {
     	this.context.pop();
     }
     
     private void WriteSString(String val)
     {
     	if ("c-attr".equals(this.context.peek().toString()))
     	{
     		Attribute attr = (Attribute) this.currentNode.peek();
     		if ("c".equals(attr.getName()))
     		{
     			this.cSpaces = Integer.parseInt(val);
     		}
     	}
     }
     // There's one more special thing to do : 
     // remove the eventual first white space on the first text node of the
     // first run of a paragraph or string item.
     
     /**
      * Paragraphs and String items
      */
      private boolean isFirstTextNode = false;
       
      private boolean IsP()
      {
      	Element e = (Element) this.currentNode.peek();
      	return "p".equals(e.getName()) && W_NAMESPACE.equals(e.getNs());
      }
      
      private void StartP()
      {
      	Element p = (Element) this.currentNode.peek();
      	this.nextWriter.WriteStartElement(p.getPrefix(), p.getName(), p.getNs());
      	this.isFirstTextNode = true;
      }
      
      private void EndP()
      {
      	this.nextWriter.WriteEndElement();
      	this.isFirstTextNode = false;
      }
      
      private boolean IsSi()
      {
      	Element e = (Element) this.currentNode.peek();
      	return "si".equals(e.getName()) && DEFAULT_NAMESPACE.equals(e.getNs());
      }
      
      private void StartSi()
      {
      	Element si = (Element) this.currentNode.peek();
      	this.nextWriter.WriteStartElement(si.getPrefix(), si.getName(), si.getNs());
      	this.isFirstTextNode = true;
      }
      
      private void EndSi()
      {
      	this.nextWriter.WriteEndElement();
      	this.isFirstTextNode = false;
      }
      
      //private  void PrintValues( IEnumerable myCollection )  {
   		//foreach ( Object obj in myCollection )
      	//Console.WriteLine("{0}", obj);
	  //}     
}
